/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 2.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Framebuffer Object API Tests.
 *
 * Notes:
 *   All gl calls are passed thru sgl2::Context class. Reasons:
 *    + Name, object allocation is tracked and live resources are freed
 *      when Context is destroyed.
 *    + Makes it possible to easily log all relevant calls into test log.
 *      \todo [pyry] This is not implemented yet
 *//*--------------------------------------------------------------------*/

#include "es2fFboApiTest.hpp"
#include "sglrGLContext.hpp"
#include "gluDefs.hpp"
#include "gluContextInfo.hpp"
#include "gluStrUtil.hpp"
#include "tcuRenderTarget.hpp"
#include "deString.h"
#include "glwFunctions.hpp"
#include "glsFboUtil.hpp"
#include "glwEnums.hpp"

#include <iterator>
#include <algorithm>

namespace deqp
{
namespace gles2
{
namespace Functional
{

using std::string;
using std::vector;
using tcu::TestLog;

using glw::GLenum;
using glw::GLint;

static void logComment (tcu::TestContext& testCtx, const char* comment)
{
	testCtx.getLog() << TestLog::Message << "// " << comment << TestLog::EndMessage;
}

static void checkError (tcu::TestContext& testCtx, sglr::Context& ctx, GLenum expect)
{
	GLenum result = ctx.getError();
	testCtx.getLog() << TestLog::Message << "// " << (result == expect ? "Pass" : "Fail") << ", expected " << glu::getErrorStr(expect) << TestLog::EndMessage;

	if (result != expect)
		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Error code mismatch");
}

static void checkEitherError (tcu::TestContext& testCtx, sglr::Context& ctx, GLenum expectA, GLenum expectB)
{
	GLenum	result	= ctx.getError();
	bool	isOk	= (result == expectA || result == expectB);

	testCtx.getLog() << TestLog::Message << "// " << (isOk ? "Pass" : "Fail") << ", expected " << glu::getErrorStr(expectA) << " or " << glu::getErrorStr(expectB) << TestLog::EndMessage;

	if (!isOk)
		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Error code mismatch");
}

static const char* getAttachmentName (GLenum attachment)
{
	switch (attachment)
	{
		case GL_COLOR_ATTACHMENT0:	return "GL_COLOR_ATTACHMENT0";
		case GL_DEPTH_ATTACHMENT:	return "GL_DEPTH_ATTACHMENT";
		case GL_STENCIL_ATTACHMENT:	return "GL_STENCIL_ATTACHMENT";
		default: throw tcu::InternalError("Unknown attachment", "", __FILE__, __LINE__);
	}
}

static const char* getAttachmentParameterName (GLenum pname)
{
	switch (pname)
	{
		case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:				return "GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE";
		case GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:				return "GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME";
		case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL:			return "GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL";
		case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE:	return "GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE";
		default: throw tcu::InternalError("Unknown parameter", "", __FILE__, __LINE__);
	}
}

static string getAttachmentParameterValueName (GLint value)
{
	switch (value)
	{
		case 0:									return "GL_NONE(0)";
		case GL_TEXTURE:						return "GL_TEXTURE";
		case GL_RENDERBUFFER:					return "GL_RENDERBUFFER";
		case GL_TEXTURE_CUBE_MAP_POSITIVE_X:	return "GL_TEXTURE_CUBE_MAP_POSITIVE_X";
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:	return "GL_TEXTURE_CUBE_MAP_NEGATIVE_X";
		case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:	return "GL_TEXTURE_CUBE_MAP_POSITIVE_Y";
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:	return "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y";
		case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:	return "GL_TEXTURE_CUBE_MAP_POSITIVE_Z";
		case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:	return "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z";
		default:
		{
			char tmp[64];
			deSprintf(tmp, sizeof(tmp), "0x%x", value);
			return string(tmp);
		}
	}
}

static void checkFboAttachmentParam (tcu::TestContext& testCtx, sglr::Context& ctx, GLenum attachment, GLenum pname, GLint expectedValue)
{
	TestLog& log = testCtx.getLog();
	log << TestLog::Message << "// Querying " << getAttachmentName(attachment) << " " << getAttachmentParameterName(pname) << TestLog::EndMessage;

	GLint value = 0xcdcdcdcd;
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, attachment, pname, &value);

	GLenum err = ctx.getError();

	if (value == expectedValue && err == GL_NO_ERROR)
		log << TestLog::Message << "// Pass" << TestLog::EndMessage;
	else
	{
		log << TestLog::Message << "// Fail, expected " << getAttachmentParameterValueName(expectedValue) << " without error" << TestLog::EndMessage;
		testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid result for attachment param query");
	}
}

static void notSupportedTest (tcu::TestContext& testCtx, sglr::Context& context)
{
	DE_UNREF(testCtx);
	DE_UNREF(context);
	throw tcu::NotSupportedError("Not supported", "", __FILE__, __LINE__);
}

static void textureLevelsTest (tcu::TestContext& testCtx, sglr::Context& context)
{
	deUint32	tex		= 1;
	deUint32	fbo		= 1;

	context.bindTexture(GL_TEXTURE_2D, tex);
	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256);
	context.texImage2D(GL_TEXTURE_2D, 1, GL_RGB, 128, 128);

	context.bindFramebuffer(GL_FRAMEBUFFER, fbo);

	static int levels[] = { 2, 1, 0, -1, 0x7fffffff, 0, 1 };

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(levels); ndx++)
	{
		context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, levels[ndx]);
		checkError(testCtx, context, levels[ndx] == 0 ? GL_NO_ERROR : GL_INVALID_VALUE);
	}
}

static void textureLevelsWithRenderToMipmapTest (tcu::TestContext& testCtx, sglr::Context& context)
{
	deUint32	tex		= 1;
	deUint32	fbo		= 1;

	context.bindTexture(GL_TEXTURE_2D, tex);
	context.texImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256);
	context.texImage2D(GL_TEXTURE_2D, 1, GL_RGB, 128, 128);

	context.bindFramebuffer(GL_FRAMEBUFFER, fbo);

	static int levels[] = { 2, 1, 0, -1, 0x7fffffff, 0, 1 };

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(levels); ndx++)
	{
		context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, levels[ndx]);
		checkError(testCtx, context, de::inBounds(levels[ndx], 0, 16) ? GL_NO_ERROR : GL_INVALID_VALUE);
	}
}

static void validTex2DAttachmentsTest (tcu::TestContext& testCtx, sglr::Context& context)
{
	context.bindFramebuffer(GL_FRAMEBUFFER, 1);
	static const GLenum attachmentPoints[] =
	{
		GL_COLOR_ATTACHMENT0,
		GL_DEPTH_ATTACHMENT,
		GL_STENCIL_ATTACHMENT
	};

	// Texture2D
	deUint32 tex2D = 1;
	context.bindTexture(GL_TEXTURE_2D, tex2D);
	for (int pointNdx = 0; pointNdx < DE_LENGTH_OF_ARRAY(attachmentPoints); pointNdx++)
	{
		context.framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoints[pointNdx], GL_TEXTURE_2D, tex2D, 0);
		checkError(testCtx, context, GL_NO_ERROR);
	}
}

static void validTexCubeAttachmentsTest (tcu::TestContext& testCtx, sglr::Context& context)
{
	static const GLenum attachmentPoints[] =
	{
		GL_COLOR_ATTACHMENT0,
		GL_DEPTH_ATTACHMENT,
		GL_STENCIL_ATTACHMENT
	};
	static const GLenum cubeTargets[] =
	{
		GL_TEXTURE_CUBE_MAP_POSITIVE_X,
		GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
		GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
		GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
		GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
		GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
	};

	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

	// TextureCube
	deUint32 texCube = 2;
	context.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
	for (int pointNdx = 0; pointNdx < DE_LENGTH_OF_ARRAY(attachmentPoints); pointNdx++)
	{
		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(cubeTargets); targetNdx++)
		{
			context.framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoints[pointNdx], cubeTargets[targetNdx], texCube, 0);
			checkError(testCtx, context, GL_NO_ERROR);
		}
	}
}

static void validRboAttachmentsTest (tcu::TestContext& testCtx, sglr::Context& context)
{
	static const GLenum attachmentPoints[] =
	{
		GL_COLOR_ATTACHMENT0,
		GL_DEPTH_ATTACHMENT,
		GL_STENCIL_ATTACHMENT
	};

	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

	// Renderbuffer
	deUint32 rbo = 3;
	context.bindRenderbuffer(GL_RENDERBUFFER, rbo);
	for (int pointNdx = 0; pointNdx < DE_LENGTH_OF_ARRAY(attachmentPoints); pointNdx++)
	{
		context.framebufferRenderbuffer(GL_FRAMEBUFFER, attachmentPoints[pointNdx], GL_RENDERBUFFER, rbo);
		checkError(testCtx, context, GL_NO_ERROR);
	}
}

static void attachToDefaultFramebufferTest (tcu::TestContext& testCtx, sglr::Context& context)
{
	logComment(testCtx, "Attaching 2D texture to default framebuffer");

	deUint32 tex2D = 1;
	context.bindTexture(GL_TEXTURE_2D, tex2D);
	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);
	checkError(testCtx, context, GL_INVALID_OPERATION);

	logComment(testCtx, "Attaching renderbuffer to default framebuffer");

	deUint32 rbo = 1;
	context.bindRenderbuffer(GL_RENDERBUFFER, rbo);
	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo);
	checkError(testCtx, context, GL_INVALID_OPERATION);
}

static void invalidTex2DAttachmentTest (tcu::TestContext& testCtx, sglr::Context& context)
{
	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

	logComment(testCtx, "Attaching 2D texture using GL_TEXTURE_CUBE_MAP_NEGATIVE_X texture target");

	deUint32 tex2D = 1;
	context.bindTexture(GL_TEXTURE_2D, tex2D);
	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, tex2D, 0);
	checkError(testCtx, context, GL_INVALID_OPERATION);

	logComment(testCtx, "Attaching deleted 2D texture object");
	context.deleteTextures(1, &tex2D);
	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);
	checkError(testCtx, context, GL_INVALID_OPERATION);
}

static void invalidTexCubeAttachmentTest (tcu::TestContext& testCtx, sglr::Context& context)
{
	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

	logComment(testCtx, "Attaching cube texture using GL_TEXTURE_2D texture target");
	deUint32 texCube = 2;
	context.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texCube, 0);
	checkError(testCtx, context, GL_INVALID_OPERATION);

	logComment(testCtx, "Attaching deleted cube texture object");
	context.deleteTextures(1, &texCube);
	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texCube, 0);
	checkError(testCtx, context, GL_INVALID_OPERATION);
}

static void invalidRboAttachmentTest (tcu::TestContext& testCtx, sglr::Context& context)
{
	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

	logComment(testCtx, "Attaching renderbuffer using GL_FRAMEBUFFER renderbuffer target");
	deUint32 rbo = 3;
	context.bindRenderbuffer(GL_RENDERBUFFER, rbo);
	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER, rbo);
	checkError(testCtx, context, GL_INVALID_ENUM);

	logComment(testCtx, "Attaching deleted renderbuffer object");
	context.deleteRenderbuffers(1, &rbo);
	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
	checkError(testCtx, context, GL_INVALID_OPERATION);
}

static void attachNamesTest (tcu::TestContext& testCtx, sglr::Context& context)
{
	context.bindFramebuffer(GL_FRAMEBUFFER, 1);

	// Just allocate some names, don't bind for storage
	deUint32 reservedTexName;
	context.genTextures(1, &reservedTexName);

	logComment(testCtx, "Attaching allocated texture name to 2D target");
	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, reservedTexName, 0);
	checkError(testCtx, context, GL_INVALID_OPERATION);

	logComment(testCtx, "Attaching allocated texture name to cube target");
	context.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, reservedTexName, 0);
	checkError(testCtx, context, GL_INVALID_OPERATION);

	deUint32 reservedRboName;
	context.genRenderbuffers(1, &reservedRboName);

	logComment(testCtx, "Attaching allocated renderbuffer name");
	context.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, reservedRboName);
	checkError(testCtx, context, GL_INVALID_OPERATION);
}

static void attachmentQueryDefaultFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	// Check that proper error codes are returned
	GLint unused = 1;
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &unused);
	checkEitherError(testCtx, ctx, GL_INVALID_ENUM, GL_INVALID_OPERATION);
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &unused);
	checkEitherError(testCtx, ctx, GL_INVALID_ENUM, GL_INVALID_OPERATION);
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &unused);
	checkEitherError(testCtx, ctx, GL_INVALID_ENUM, GL_INVALID_OPERATION);
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &unused);
	checkEitherError(testCtx, ctx, GL_INVALID_ENUM, GL_INVALID_OPERATION);
}

static void attachmentQueryEmptyFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	static const GLenum attachmentPoints[] =
	{
		GL_COLOR_ATTACHMENT0,
		GL_DEPTH_ATTACHMENT,
		GL_STENCIL_ATTACHMENT
	};

	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(attachmentPoints); ndx++)
		checkFboAttachmentParam(testCtx, ctx, attachmentPoints[ndx], GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);

	// Check that proper error codes are returned
	GLint unused = -1;
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &unused);
	checkError(testCtx, ctx, GL_INVALID_ENUM);
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &unused);
	checkError(testCtx, ctx, GL_INVALID_ENUM);
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &unused);
	checkError(testCtx, ctx, GL_INVALID_ENUM);
}

static void es3AttachmentQueryEmptyFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	// Error codes changed for ES3 so this version of test should be
	// used when ES2 context is created on ES3 capable hardwere

	static const GLenum attachmentPoints[] =
	{
		GL_COLOR_ATTACHMENT0,
		GL_DEPTH_ATTACHMENT,
		GL_STENCIL_ATTACHMENT
	};

	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(attachmentPoints); ndx++)
		checkFboAttachmentParam(testCtx, ctx, attachmentPoints[ndx], GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);

	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 0);

	// Check that proper error codes are returned
	GLint unused = -1;
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &unused);
	checkError(testCtx, ctx, GL_INVALID_OPERATION);
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &unused);
	checkError(testCtx, ctx, GL_INVALID_OPERATION);
}

static void attachmentQueryTex2DTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	ctx.bindTexture(GL_TEXTURE_2D, 1);
	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 1, 0);

	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 1);
	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, 0);
	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, 0);
}

static void attachmentQueryTexCubeTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	ctx.bindTexture(GL_TEXTURE_CUBE_MAP, 2);
	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 2, 0);

	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 2);
	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, 0);
	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y);
}

static void attachmentQueryRboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	ctx.bindRenderbuffer(GL_RENDERBUFFER, 3);
	ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 3);

	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, 3);

	GLint unused = 0;
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &unused);
	checkError(testCtx, ctx, GL_INVALID_ENUM);
	ctx.getFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &unused);
	checkError(testCtx, ctx, GL_INVALID_ENUM);
}

static void deleteTex2DAttachedToBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	deUint32 tex2D = 1;
	ctx.bindTexture(GL_TEXTURE_2D, tex2D);
	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);

	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, tex2D);

	ctx.deleteTextures(1, &tex2D);

	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
}

static void deleteTexCubeAttachedToBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	deUint32 texCube = 1;
	ctx.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texCube, 0);

	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, texCube);

	ctx.deleteTextures(1, &texCube);

	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
}

static void deleteRboAttachedToBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	deUint32 rbo = 1;
	ctx.bindRenderbuffer(GL_RENDERBUFFER, rbo);
	ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, rbo);

	ctx.deleteRenderbuffers(1, &rbo);

	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_NONE);
}

static void deleteTex2DAttachedToNotBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	deUint32 tex2D = 1;
	ctx.bindTexture(GL_TEXTURE_2D, tex2D);
	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);

	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, tex2D);

	ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);

	ctx.deleteTextures(1, &tex2D);

	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
	checkFboAttachmentParam(testCtx, ctx, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, tex2D);
}

static void deleteTexCubeAttachedToNotBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	deUint32 texCube = 1;
	ctx.bindTexture(GL_TEXTURE_CUBE_MAP, texCube);
	ctx.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texCube, 0);

	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, texCube);

	ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);

	ctx.deleteTextures(1, &texCube);

	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_TEXTURE);
	checkFboAttachmentParam(testCtx, ctx, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, texCube);
}

static void deleteRboAttachedToNotBoundFboTest (tcu::TestContext& testCtx, sglr::Context& ctx)
{
	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	deUint32 rbo = 1;
	ctx.bindRenderbuffer(GL_RENDERBUFFER, rbo);
	ctx.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, rbo);

	ctx.bindFramebuffer(GL_FRAMEBUFFER, 0);

	ctx.deleteRenderbuffers(1, &rbo);

	ctx.bindFramebuffer(GL_FRAMEBUFFER, 1);

	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, GL_RENDERBUFFER);
	checkFboAttachmentParam(testCtx, ctx, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, rbo);
}

class FboApiCase : public TestCase
{
public:
	typedef void (*TestFunc) (tcu::TestContext& testCtx, sglr::Context& context);

								FboApiCase				(Context& context, const char* name, const char* description, TestFunc test);
	virtual						~FboApiCase				(void);

	virtual IterateResult		iterate					(void);

private:
								FboApiCase				(const FboApiCase& other);
	FboApiCase&					operator=				(const FboApiCase& other);

	TestFunc					m_testFunc;
};

FboApiCase::FboApiCase (Context& context, const char* name, const char* description, TestFunc test)
	: TestCase		(context, name, description)
	, m_testFunc	(test)
{
}

FboApiCase::~FboApiCase (void)
{
}

TestCase::IterateResult FboApiCase::iterate (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	GLU_EXPECT_NO_ERROR(gl.getError(), "Before test case");

	// Initialize result to PASS
	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	// Execute test case
	{
		sglr::GLContext context(m_context.getRenderContext(), m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(0, 0, m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight()));
		m_testFunc(m_testCtx, context);
	}

	GLU_EXPECT_NO_ERROR(gl.getError(), "After test case");

	return STOP;
}

FboApiTestGroup::FboApiTestGroup (Context& context)
	: TestCaseGroup(context, "api", "API Tests")
{
}

FboApiTestGroup::~FboApiTestGroup (void)
{
}

void FboApiTestGroup::init (void)
{
	std::set<std::string> extensions;
	std::copy(m_context.getContextInfo().getExtensions().begin(), m_context.getContextInfo().getExtensions().end(), std::inserter(extensions, extensions.begin()));

	const glu::RenderContext& renderContext = m_context.getRenderContext();
	bool  defaultFboIsZero	= renderContext.getDefaultFramebuffer() == 0;
	bool  isES3Compatible	= gls::FboUtil::checkExtensionSupport(renderContext, "DEQP_gles3_core_compatible");
	bool  hasRenderToMipmap	= isES3Compatible || extensions.find("GL_OES_fbo_render_mipmap") != extensions.end();

	// Valid attachments
	addChild(new FboApiCase(m_context, "valid_tex2d_attachments",					"Valid 2D texture attachments",							validTex2DAttachmentsTest));
	addChild(new FboApiCase(m_context, "valid_texcube_attachments",					"Valid cubemap attachments",							validTexCubeAttachmentsTest));
	addChild(new FboApiCase(m_context, "valid_rbo_attachments",						"Valid renderbuffer attachments",						validRboAttachmentsTest));

	// Invalid attachments
	addChild(new FboApiCase(m_context, "attach_to_default_fbo",						"Invalid usage: attaching to default FBO",				defaultFboIsZero ? attachToDefaultFramebufferTest : notSupportedTest));
	addChild(new FboApiCase(m_context, "invalid_tex2d_attachments",					"Invalid 2D texture attachments",						invalidTex2DAttachmentTest));
	addChild(new FboApiCase(m_context, "invalid_texcube_attachments",				"Invalid cubemap attachments",							invalidTexCubeAttachmentTest));
	addChild(new FboApiCase(m_context, "invalid_rbo_attachments",					"Invalid renderbuffer attachments",						invalidRboAttachmentTest));
	addChild(new FboApiCase(m_context, "attach_names",								"Attach allocated names without objects",				attachNamesTest));

	addChild(new FboApiCase(m_context, "texture_levels",							"Valid and invalid texturel levels",					hasRenderToMipmap ? textureLevelsWithRenderToMipmapTest : textureLevelsTest));

	// Attachment queries
	addChild(new FboApiCase(m_context, "attachment_query_default_fbo",				"Query attachments from default FBO",					defaultFboIsZero ? attachmentQueryDefaultFboTest : notSupportedTest));
	addChild(new FboApiCase(m_context, "attachment_query_empty_fbo",				"Query attachments from empty FBO",						isES3Compatible ? es3AttachmentQueryEmptyFboTest : attachmentQueryEmptyFboTest));
	addChild(new FboApiCase(m_context, "attachment_query_tex2d",					"Query 2d texture attachment properties",				attachmentQueryTex2DTest));
	addChild(new FboApiCase(m_context, "attachment_query_texcube",					"Query cubemap attachment properties",					attachmentQueryTexCubeTest));
	addChild(new FboApiCase(m_context, "attachment_query_rbo",						"Query renderbuffer attachment properties",				attachmentQueryRboTest));

	// Delete attachments
	addChild(new FboApiCase(m_context, "delete_tex_2d_attached_to_bound_fbo",		"Delete 2d texture attached to currently bound FBO",	deleteTex2DAttachedToBoundFboTest));
	addChild(new FboApiCase(m_context, "delete_tex_cube_attached_to_bound_fbo",		"Delete cubemap attached to currently bound FBO",		deleteTexCubeAttachedToBoundFboTest));
	addChild(new FboApiCase(m_context, "delete_rbo_attached_to_bound_fbo",			"Delete renderbuffer attached to currently bound FBO",	deleteRboAttachedToBoundFboTest));

	addChild(new FboApiCase(m_context, "delete_tex_2d_attached_to_not_bound_fbo",	"Delete 2d texture attached to FBO",					deleteTex2DAttachedToNotBoundFboTest));
	addChild(new FboApiCase(m_context, "delete_tex_cube_attached_to_not_bound_fbo",	"Delete cubemap attached to FBO",						deleteTexCubeAttachedToNotBoundFboTest));
	addChild(new FboApiCase(m_context, "delete_rbo_attached_to_not_bound_fbo",		"Delete renderbuffer attached to FBO",					deleteRboAttachedToNotBoundFboTest));
}

} // Functional
} // gles2
} // deqp
